构造函数语义学——Member Initialization List

(5.4-5.9由于开题事宜断更,即日起恢复更新)

前言

 
当写下一个constructor时我们可以设定class members的初值:它们要么经由Member Initialization List,要么在construtor内部处理(除了4种情况必须使用member initialization list)。


何时必须使用member initialization list

 
以下四种情况必须使用member initialization list:

  1. 初始化一个reference member
  2. 初始化一个const member
  3. 调用一个base class的constructor,而它拥有一组参数
  4. 当调用一个member class的constructor,而它拥有一组参数

member initialization list详解

不使用member initialization list

考虑如下程序:

1
2
3
4
5
6
7
8
9
class Word{
String _name;
int _cnt;
public:
Word(){
_name=0;
_cnt=0;
}
};

该程序能够正确编译并执行,只是效率偏低。编译器在执行上述程序扩张代码使之生成一个临时对象,如下所示:
1
2
3
4
5
6
7
Word::Word(/*this poniter*/){
_name.String::String();
String temp = String(nullptr);
_name.String::operator=(temp);
temp.String::~String();
_cnt=0;
}

为了保证高效,我们应当做到对任何初始化操作都执行member initialization list。

member initialization list的作用

当构造函数中出现member initialization list后,编译器会一一操作member initialization list,以member声明次序(而非list内部次序)在constructor之内安插初始化操作,次序问题极易引发危险操作,例如:

1
2
3
4
5
6
class X{
int i;
int j;
public:
X(int val):j(val),i(j) {}
};

将会被扩张为:
1
2
3
4
X::X(int val){
i=j;//初始化失败
j=bal;
}


member initialization list的疑难点

二探次序

如果member initialization list中的项目被安插到constructor中,会继续保存声明次序吗?

1
2
3
4
X::X(int val)
:j(val){
i=j;
}

j的初始化操作会安插在explicit user assignment之前或者是之后?答案是initialization list的项目总会被放在explicit user code之前。

initialization list与member function

能否调用一个member function以设定一个member的初值?

1
2
3
X::X(int val)
:i(xfoo(val)),j(val)//xfoo是X的一个member function
{}

答案是可行的。但是…务必使用“存在于constructor体内的一个member”,而非“存在于member initialization list中的member”,来为另一个member设定初值。因为我们无法了解xfoo()对object的依赖性,在确保了xfoo()在constructor内部之后,对于“究竟是哪一个member在xfoo()执行时被设立初值”,就不会造成歧义。

member function的使用是合法的,是建立在和此object相关的this指针已经就位(我们暂且忽略与该member function相关的member),此时原代码大致被扩张为:

1
2
3
4
X::X(/*this pointer*/){
i = this->xfoo(val);
j = val;
}

如果一个derived class member function被调用,其返回值作为base class constructor的参数呢?
1
2
3
4
5
6
7
class FooBar:public X{
int _fval;
public:
int fval() {return _fval;}
FooBar(int val):_fval(val),X(fval()) {}
...
}

其扩张结果如下:
1
2
3
4
FooBar::FooBar(/*this pointer*/){
X::X(this,this->fval());
_fval =val;
}

显然,这确实不合时宜。


总结

 
简单地说,编译器会对initialization list一一处理并且重新排序,以反映出member的声明次序。它会安插部分代码到constructor体内,并置于任何explicit user code之前。